#!/usr/bin/python

"""
A module is a special kind of cellfield that has the ability to execute 
operations
"""

import dot_parser
import string

from utils import *
from logger import logger
from interconnect import Interconnect
from low_level import Cellfield, Cell

WRITE_FLAG = 1

class IO(Cellfield):
    """
    an input/output node in the system
    NOTE: unsupported at this time
    """
    
    def __init__(self):
        """
        initialize the I/O node
        TODO: it would be better if this didn't have a cellfield
        """
        self.value = ''
        Cellfield.__init__(self, 1, 1, 'O')
    
    def add_input(self, inval):
        """
        set the stored value
        """
        self.value = inval
    
    def clear_inputs(self):
        """
        clear the stored value
        """
        pass
    
    def compute(self):
        """
        return the stored value
        """
        return self.value
    
    def get_result(self):
        """
        return the stored value
        """
        return self.value

class ModuleError(Exception):
    """
    handling class for module exceptions
    """
    
    def __init__(self, culprit, value):
        """
        initialize the class
        """
        logger.error("module: %s: %s" % (culprit, value))

class Module(Cellfield):
    """
    a module is a cellfield that performs a specific operation
    """
    
    def __init__(self, type = 'empty'):
        """
        initialize the module
        """
        
        #initialize the module dictionary if it's empty
        if len(module_dict) == 0:
            load_module_lib()
           
        #define the module's cells according to the module dictionary
        if type not in module_dict:
            raise ModuleError("type not defined", type)
        
        dimension = len(module_dict[type].cells[0])
        proportion = dimension / len(module_dict[type].cells)

        #set up the cellfield
        Cellfield.__init__(self, dimension, proportion, type)
        
        self.inputs = []
        self.result = ''
        self.parents = []
        self.children = []
        self.area = dimension * dimension / proportion
        self.delay = dimension + dimension / proportion
        
    def set_type(self, type):
        """
        set up the module according to its type
        
        note: overrides set_type from the Cellfield class
        """
            
        #update the module type
        self.type = type
        
        #keep track of whether we're in math mode or memory mode
        self.mode = module_dict[self.type].mode
        
        #the module type definition contains a 2-d array of cells
        self.cellfield = []
        for celltype_row in module_dict[type].cells:
            #build a row of cells
            cellfield_row = []
            for celltype in celltype_row:
                #add a cell of the specified type
                cellfield_row.append(Cell(celltype))
            #add the row to the cellfield
            self.cellfield.append(cellfield_row)

    def get_addr(self, cell_name):
        cellarray = module_dict[self.type].names
        
        for row in range(len(cellarray)):
            for col in range(len(cellarray[0])):
                if cell_name == cellarray[row][col]:
                    return (row, col)
        
        return cell_name

    def process_inputs(self):
        """
        return a dictionary in the form {(row, col): [in1, in2, in3, ...], ...}
        """
        sources = []
        slices = []

        cellnames = []
        for list in module_dict[self.type].names:
            for entry in list:
                cellnames.append(entry)
        
        input_dict = dict((self.get_addr(x), []) for x in cellnames)

        in_list = module_dict[self.type].inputs

        split_inputs = []
        for input in self.inputs:
            split_input = []
            for slice_num in range(len(input)/4):
                split_input.append(input[slice_num * 4 : slice_num * 4 + 4])
            split_inputs.append(split_input)
        
        for sources, split_input in zip(in_list, split_inputs):
            #each cell needs an input value, default to zero
            for key in input_dict.keys():
                input_dict[key].append('0000')
            #if the library file specifies, change the default value
            for source_list in sources:
                for name, value in zip(source_list, split_input):
                    addr = self.get_addr(name)
                    input_dict[addr][-1] = value

        return input_dict
    
    def process_routes(self):
        route_dict = {}
        
        routing_dot = module_dict[self.type].routing
        
        dot = dot_parser.parse_dot_data(routing_dot)
        
        if dot == None:
            raise ModuleError('incorrect DOT file format')
        
        dot_edge_list = dot.get_edge_list()
        for edge in dot_edge_list:
            source = self.get_addr(edge.get_source())
            dest = self.get_addr(edge.get_destination())
            msb_lsb = edge.__get_attribute__("chunk")
            index = edge.__get_attribute__("index")
            if source not in route_dict:
                route_dict[source] = []
            route_dict[source].append([dest, msb_lsb, int(index)])

        return route_dict
    
    def process_results(self, result_dict):
        result_list = []
        name_list = module_dict[self.type].outputs
        for name in name_list:
            result_list.append(result_dict[name])
        return ''.join(result_list)
    
    def sanitize_inputs(self, input_set):
        while len(input_set) < 4:
            input_set[:0] = ['']
        
        [a, b, c, d] = input_set[:4]
        
        return [string.zfill(a, 4),
                string.zfill(b, 4),
                string.zfill(c, 4),
                string.zfill(d, 4)]
    
    def compute(self):
        if self.mode == 'memory':
            self.result = self.compute_memory()
        else:
            self.result = self.compute_math()
        return self.result
    
    def compute_memory(self):
        """
        perform the module computation (memory mode)
        """
        result = ''
        
        if len(self.inputs) != 0:
            addr = self.inputs[0]
            
            cellrow = int(addr[:2], 2)
            elemrow = int(addr[2:6], 2)
            read = (addr[6] != WRITE_FLAG)
            
            #TODO address calculations assume single cell, allow larger memories
            cell = self.cellfield[0][0]
            
            if read:
                elements = cell.matrix[cellrow]
                for element in elements:
                    result += element.read(elemrow, 0)
                    result += element.read(elemrow, 1)
            else:
                #TODO element modules are read-only, add write ability
                pass
        else:
            result = '00000000'

        return result
    
    def compute_math(self):
        """
        perform the module computation (math mode)
        """
        output_dict = {}
        result_dict = {}
        
        #process the routing directions
        route_dict = self.process_routes()
        
        #pair up inputs with cells
        input_dict = self.process_inputs()
        
        #loop: run cell computations, perform routing
        for loop in range(self.delay):
            #apply arguments to the cells and let them compute
            for (addr_row, addr_col) in input_dict.keys():
                input_set = input_dict[(addr_row, addr_col)]
                #initialize and pad with zeros
                [a, b, c, d] = self.sanitize_inputs(input_set)
                result = self.cellfield[addr_row][addr_col].compute(a, b, c, d)
                output_dict[(addr_row, addr_col)] = result
 
            #handle the cell results
            for (src_row, src_col) in sorted(output_dict.keys()):
                #split the output string into is MSB, LSB parts
                out = output_dict[(src_row, src_col)]
                half = len(out)/2

                #use the routing info
                for entry in route_dict[(src_row, src_col)]:
                    [dest, msb_lsb, index] = entry
                    
                    if msb_lsb == 'msb':
                        value = out[:half]
                    else:
                        value = out[half:]
                        
                    if type(dest) is tuple:
                        input_dict[dest][index] = value
                    else:
                        result_dict[dest] = value

        #aggregate and return the results
        return self.process_results(result_dict)
        
    def add_input(self, operand):
        """
        set the input operands of a structure
        """
        self.inputs.append(operand)
        
    def clear_inputs(self):
        """
        clear the list of inputs, in preparation for new ones
        """
        self.inputs = []

    def get_inputs(self):
        """
        return the current value of the input
        """
        return self.inputs
               
    def get_result(self):
        """
        get the output value computed by the structure
        """
        return self.result
        
    def add_child(self, childname):
        """
        add the name of a child to the list of children
        """
        self.children.append(childname)
